package centralized;

import java.io.IOException;
import java.util.*;

import common.*;

public class Middleware extends Thread{
	public Hashtable<NodeRecord, Node> routing_table;
	private ArrayList<Integer> ip_pool;
	private Long system_up_time;
	private boolean b_firstnode = true;
	private long ip = Def.MIDDLEWARE_IP;	// always 192.168.0.1	//3232235521L;	
	
	private long master_ip;
	private int master_pid;
	
	public Middleware(){
		routing_table = new Hashtable<NodeRecord, Node>();
		ip_pool = new ArrayList<Integer>();
		
		// generates available IP pool (prevent duplicate IP's are generated by rand()).
		for( int i = 0; i < Config.MAX_NODES; i++ ){
			while( true ){
				Integer gen = ( new Random() ).nextInt( Config.MAX_NODES * 2 ) + 2;
				
				if( !ip_pool.contains( gen ) ){
					ip_pool.add( gen );
					break;
				}
			}
		}
		
		// initial NTP time receiving.
		try{
			system_up_time = Utils.get_ntp_time();
		}catch( IOException e ){
			e.printStackTrace();
		}//System.currentTimeMillis();
	}
	
	public void run(){	// periodically prints the current time
		int i = 0;
		long ntp = 0;
		
		while( true ){
			i++;
			
			
			if( Config.REPORT )
				reporting( i );
			
			
			// NTP
			try {
				ntp = Utils.get_ntp_time();
			} catch (IOException e) {
				e.printStackTrace();
			}//System.currentTimeMillis();
			print_log( i * Config.REPORT_INTERVAL  + "min: Current time = " + ntp + " (ms)" );
			//print_log( i * Config.REPORT_INTERVAL  + "min: Current time = " + System.currentTimeMillis() + " (ms)" );
			
			
			try{
				sleep( 1000 * 60 * Config.REPORT_INTERVAL );	// 10 min
			}catch( InterruptedException e ){
				e.printStackTrace();
			}
		}
	}
	
	/**
	 * Method: connect() - allows nodes to connect to the network
	 * @return newIp = ip addr assigned to the node
	 * 				or -1 on err
	 */
	public synchronized long connect( int new_pid, Node new_node ){	// --- should be in CS
		long new_ip = -1;
		
		if( ip_pool.isEmpty() ){
			print_log( "no IP is available. The network is full.", Def.STDERR );
			return new_ip;
		}
		
		new_ip = ip_pool.remove( 0 );
		new_ip += 3232235520L;	// 192.168.0.x
		
		routing_table.put( new NodeRecord( new_pid, new_ip ), new_node ); //newMacAddr ) );
		
		// only the first node gets NTP time as the initial value
		if( b_firstnode ){
			//new_node.set_localtime_ms( System.currentTimeMillis() );
			try{
				new_node.set_localtime_s( Utils.get_ntp_time() );
			}catch ( IOException e ){
				e.printStackTrace();
			}
			new_node.set_master( new_pid, new_ip );
			
			master_ip = new_ip; 
			master_pid = new_pid;
			b_firstnode = false;
		}
		
		return new_ip;
	}
	
	public synchronized boolean disconnect( long ip, int pid ){	// --- should be in CS
		int old_ip = ( int )( ip - 3232235520L );	// 192.168.0.x
		
		if( ip_pool.contains( old_ip ) ){
			print_log( "IP " + ip + " has been already released.", Def.STDERR );
			return false;
		}
		
		if( ip_pool.add( old_ip ) ){
			routing_table.remove( new NodeRecord( pid, ip ) );
			print_log( "IP " + ip + " has been successfully released." );
			return true;
		}else
			print_log( "IP " + ip + " cannot be released.", Def.STDERR );
		
		return false;
	}
	
	public void print_log( String log ){
		print_log( log, Def.STDOUT );
	}
	
	public void print_log( String log, byte target ){
		if( !Config.VERBOSE )
			return;
		
		long ntp = 0;
		
		try{
			ntp = Utils.get_ntp_time();
		}catch( IOException e ){
			e.printStackTrace();
		}
		
		
		if( target == Def.STDOUT ){
			//System.out.print( "*[MSG|router:" + Utils.ipToString( ip ) + "(" + System.currentTimeMillis() + ")] " );
			System.out.print( "*[MSG|router:" + Utils.ipToString( ip ) + "(" + ntp + ")] " );
			System.out.println( log );
		}else if( target == Def.STDERR ){
			//System.err.print( "*[ERR|router:" + Utils.ipToString( ip ) + "(" + System.currentTimeMillis() + ")] " );
			System.err.print( "*[ERR|router:" + Utils.ipToString( ip ) + "(" + ntp + ")] " );
			System.err.println( log );
		}
	}
	
	public void set_master( long ip, int pid ){
		master_ip = ip;
		master_pid = pid;
	}
	
	public void reporting( int i ){
		Hashtable<Node, Long> timediff_master;
        Hashtable<Node, Long> timediff_system;

        timediff_master = new Hashtable<Node, Long>();
        timediff_system = new Hashtable<Node, Long>();
        
		// print information periodically

			timediff_master.clear();
			timediff_system.clear();

			Collection<Node> all_nodes = routing_table.values();
			Iterator<Node> itr = all_nodes.iterator();

			long mastertime = 0, masterdrift = 0, systemtime = 0;
			boolean hasElement = false;

			//systemtime = System.currentTimeMillis() / 1000;

			try{
				systemtime = Utils.get_ntp_time();
			}catch( IOException e ){
				e.printStackTrace();
			}
			
			if (itr.hasNext())
				hasElement = true;

			while( itr.hasNext() ){
				Node br_rcv = itr.next();       // br_rcv = broadcast, receiver

						// get the time of master node
						if (br_rcv.is_master()) {
							mastertime = br_rcv.get_localtime_s();
							masterdrift = br_rcv.get_drift();
						}
			}

			itr = all_nodes.iterator();
			while( itr.hasNext() ){
				Node br_rcv = itr.next();       // br_rcv = broadcast, receiver

				timediff_master.put(br_rcv, mastertime - br_rcv.get_localtime_s());
				timediff_system.put(br_rcv, systemtime - br_rcv.get_localtime_s());
			}

			if (hasElement) {
				print_log( "\nSystem report (" + i * Config.REPORT_INTERVAL  + " min elapsed)");
				System.out.println("Master time = " + mastertime + "s with d=" + masterdrift + " / System time = " + systemtime + "s)\n");                      

				System.out.print(" IP/PID                ");
				System.out.println(" TimeDiff (master)  TimeDiff (system)");

				double avg_master = 0.0, avg_system = 0.0;
				double mse_master = 0.0, mse_system = 0.0;

				itr = all_nodes.iterator();

				while ( itr.hasNext() ){
					Node br_rcv = itr.next();

					if (br_rcv.is_master()) {
						System.out.print("*");
						System.out.print(String.format("%-15s/%5d", Utils.ipToString(br_rcv.get_ip()), br_rcv.get_pid())); 
					}
					else
						System.out.print(String.format(" %-15s/%5d", Utils.ipToString(br_rcv.get_ip()), br_rcv.get_pid())); 

					System.out.print("  ");
					System.out.print(String.format("%-17d", timediff_master.get(br_rcv)));
					System.out.print("  ");
					System.out.print(String.format("%-17d", timediff_system.get(br_rcv)));
					System.out.println("");

					avg_master += (timediff_master.get(br_rcv));
					avg_system += (timediff_system.get(br_rcv));
					mse_master += (timediff_master.get(br_rcv) * timediff_master.get(br_rcv));
					mse_system += (timediff_system.get(br_rcv) * timediff_system.get(br_rcv));
				}

				avg_master /= timediff_master.size();
				avg_system /= timediff_system.size();
				mse_master /= timediff_master.size();
				mse_system /= timediff_system.size();

				System.out.println(String.format("%22s  %-17.2f  %-17.2f", "AVG", avg_master, avg_system));
				System.out.println(String.format("%22s  %-17.2f  %-17.2f", "MSE", mse_master, mse_system) + "\n");
			}

		}
}
